﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Cryptography;
using System.Text;
using System.Windows;

namespace DefensiveShield
{
    public static partial class OS
    {
        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- GET FILE TYPE --------------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция определения типа файла
            @return FileType - тип файла
        */
        public static FileType GetFileType(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
            {
                return FileType.Directory;
            }
            var fileInfo = new FileInfo(fileName);
            if ((fileInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
                return FileType.Directory;
            else if (fileInfo.Extension == ".exe")
                return FileType.Executable;
            else if (fileInfo.Extension == ".txt" ||
                     fileInfo.Extension == DefensiveShield.Properties.Resources.SecretFileExtention)
                return FileType.TextFile;
            else
                return FileType.Other;
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- DELETE ITEM ----------------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция удаления объекта файловой системы
             * 
             * Данная функция проверяет права доступа пользователя к данному объекту в соответствии с мандатной и дискреционной моделью доступа, и в случае успеха удаляет его (в дискреционной модели необходимы права ReadWrite).
             * 
             * Если объектом является файл, то он должен быть зарегистрирован в системе.
             * Если объектом является директория, то она и всё её содержимое просмотренное рекурсивно должно быть зарегистрированно в системе.
             * Удалять программы и каталоги, содержащие программы, нельзя.
             * 
             * Удаление происходит как в рамках рассматриваемой системы, так и в самой файловой системе
             * 
            @return код ошибки, при её наличии
        */
        public static ErrorCodes DeleteItem(OsSession session, string fileName)
        {
            try
            {
                if (!Files.ContainsKey(fileName))
                {
                    LogMessage(session.UserName, "Attempt to delete unregistered item.", fileName);
                    return ErrorCodes.DeleteAttemptOfUnregisteredFile;
                }

                if (GetFileType(fileName) == FileType.Executable)
                {
                    LogMessage(session.UserName, "Attempt to delete executable.", fileName);
                    return ErrorCodes.DeleteAttemptOfExecutable;
                }

                if (CheckMandatoryAccessPermission(session, fileName) != ErrorCodes.NoError 
                    || GetDiscretionalAccessPermission(session, fileName) != Rights.ReadWrite)
                {
                    LogMessage(session.UserName, "ACCESS VIOLATION: not enough rights to delete item.", fileName);
                    return ErrorCodes.NotEnoughPermissionsToDeleteFile;
                }

                var fileType = GetFileType(fileName);
                if (fileType == FileType.Directory)
                {                    
                    // пытаемся удалить всё содержимое папки
                    var fileList = Directory.EnumerateFileSystemEntries(fileName, "*.*", SearchOption.AllDirectories).ToList();
                    if (fileList != null)
                    {
                        foreach (var f in fileList)
                        {
                            if (!Files.ContainsKey(f))
                            {
                                LogMessage(session.UserName, "Attempt to delete folder with unregistered item " + f, fileName);
                                return ErrorCodes.DeleteAttemptOfUnregisteredFile;
                            }

                            if (GetFileType(f) == FileType.Executable)
                            {
                                LogMessage(session.UserName, "Attempt to delete folder with executable " + f, fileName);
                                return ErrorCodes.DeleteAttemptOfExecutable;
                            }

                            if (CheckMandatoryAccessPermission(session, f) != ErrorCodes.NoError
                                || GetDiscretionalAccessPermission(session, f) != Rights.ReadWrite)
                            {
                                LogMessage(session.UserName, "ACCESS VIOLATION: not enough rights to delete item in folder " + fileName, f);
                                return ErrorCodes.NotEnoughPermissionsToDeleteFile;
                            }
                        }
                    }
                    // нарушений не выявлено
                    // Удаляем всё из операционки
                    Directory.Delete(fileName, true);

                    // удаляем всё из системы Defensive shield
                    if (fileList != null)
                    {
                        foreach (var f in fileList)
                        {
                            if (Files.ContainsKey(f))
                            {
                                Files.Remove(f);
                                LogMessage(session.UserName, "File system object deleted successfully.", f);
                            }
                        }
                    }
                    // удаляем саму папку
                    Files.Remove(fileName);                    
                }
                else 
                {
                    // удаляем файл из операционки
                    File.Delete(fileName); 

                    // удаляем файл из системы Defensive shield
                    if (Files.ContainsKey(fileName))                
                    {
                        Files.Remove(fileName);
                    }
                }
            }
            catch (Exception except)
            {
                LogMessage(session.UserName, "Error in deleting file system object. Exception: " + except.Message, fileName);
                return ErrorCodes.NotEnoughPermissionsToDeleteFile;
            }

            LogMessage(session.UserName, "File system object deleted successfully.", fileName);
            return ErrorCodes.NoError;
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- CREATE DIRECTORY -----------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция создания директории
             * 
             * Данная функция создаёт и регистрирует в системе директорию, назначая ей права в соответствии с тем, какие права прописаны у пользователя, её создавшего.
             * Также должны быть проверены права на доступ этого пользователя к папке, в которой создаётся директория.
             * Проверяется, есть ли у пользователя доступ к папке на запись, в соответствии с дискреционной и мандатной моделью разграничения доступа.
             * 
            @return код ошибки, при её наличии
        */
        public static ErrorCodes CreateDirectory(OsSession session, string directoryName)
        {
            try
            {                
                var parentDirectory = Path.GetDirectoryName(directoryName);
                if (Files.ContainsKey(parentDirectory))
                {
                    // проверка прав доступа к parentDirectory (и всем её предкам) на запись
                    if (CheckMandatoryAccessPermission(session, parentDirectory) != ErrorCodes.NoError
                       || GetDiscretionalAccessPermission(session, parentDirectory) != Rights.ReadWrite)
                    {
                        LogMessage(session.UserName, "ACCESS VIOLATION: not enough rights to create directory in " + parentDirectory, directoryName);
                        return ErrorCodes.NotEnoughPermissionsToCreateDirectory;
                    }
                }
                Directory.CreateDirectory(directoryName);
                RegisterFileSystemObject(session, directoryName);
            }
            catch (Exception except)
            {
                LogMessage(session.UserName, "Error in creating directory. Exception: " + except.Message, directoryName);
                return ErrorCodes.NotEnoughPermissionsToCreateDirectory;
            }

            LogMessage(session.UserName, "Directory created successfully.", directoryName);
            return ErrorCodes.NoError;
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- GET DIRECTORY SUB ELEMENTS -
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция возвращающая содержимое директории
             * 
             * Возвращая список содержимого директории, функция учитывает права доступа ко всем элементам внутри неё и доступ к самой директории.
             * Проверка доступа пользователя производится в соответствии с дискреционной и мандатной моделью доступа.
             * 
            @return List<string> - список элементов внутри директории
            @return null - в случае ошибки
        */
        public static List<string> GetDirectorySubElements(OsSession session, string directoryName)
        {
            try
            {
                // Patch for root directories
                if (string.IsNullOrEmpty(directoryName))
                {
                    LogMessage(session.UserName, "Contents of root directory obtained successfully.");
                    return new List<string>(Directory.GetLogicalDrives());
                }

                if (GetFileType(directoryName) != FileType.Directory)
                {
                    LogMessage(session.UserName, "Trying to access SubElements of non-directory object.", directoryName);
                    return null;
                }

                if (Files.ContainsKey(directoryName)
                    && (CheckMandatoryAccessPermission(session, directoryName) != ErrorCodes.NoError
                       || GetDiscretionalAccessPermission(session, directoryName) < Rights.Read))
                {
                    LogMessage(session.UserName, "ACCESS VIOLATION: not enough rights to read directory contents.", directoryName);
                    return null;
                }                
                List<string> allFiles = new List<string>(Directory.EnumerateFileSystemEntries(directoryName));
                LogMessage(session.UserName, "Contents of directory obtained successfully.", directoryName);
                return (from f in allFiles
                        where (!Files.ContainsKey(f) || 
                               (Files.ContainsKey(f)
                               && CheckMandatoryAccessPermission(session, f) == ErrorCodes.NoError
                               && GetDiscretionalAccessPermission(session, f) >= Rights.Read))
                        select f).ToList();
            }
            catch(Exception except)
            {
                LogMessage(session.UserName, "OS error while accessing directory. Exception: " + except.Message, directoryName);
                return null;
            }     
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- GET PARENT DIRECTORY -------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция получения родительской директории для объекта файловой системы
             * Для родительской директории также проверяются права доступа пользователя (на чтение), если она отслеживается системой
            @return string - полное имя родительской папки
            @return string("") - в случае ошибки
        */
        public static string GetParentDirectory(OsSession session, string directoryName)
        {            
            string parentDirectory;
            try
            {
                parentDirectory = Directory.GetParent(directoryName).FullName;

                // проверка прав доступа к parentDirectory на чтение в случае, если папка отслеживается
                if (Files.ContainsKey(parentDirectory) 
                    && (CheckMandatoryAccessPermission(session, parentDirectory) != ErrorCodes.NoError
                        || GetDiscretionalAccessPermission(session, parentDirectory) < Rights.Read))
                {
                    LogMessage(session.UserName, "ACCESS VIOLATION: not enough rights to access parent directory.", parentDirectory);
                    return "";
                }                
            }
            catch (Exception except)
            {
                LogMessage(session.UserName, "Error in getting parent directory. Exception: " + except.Message, directoryName);
                return "";
            }

            LogMessage(session.UserName, "Parent directory obtained successfully.", parentDirectory);
            return parentDirectory;
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- EXECUTE FILE ---------------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция исполнения файла
             * Если пользователь имеет доступ к файлу в соответствии с принципом разграничения прав, и файл является executable, то он запускается, если файл не Executable, а Other - то его тоже нужно запустить на исполнение, и windows сама определит по типу файла, как именно его нужно открыть
             * Пользователь имеет право запустить файл, если у него есть права ReadWrite и есть соответствующий гриф
            @return код ошибки, при её наличии
        */
        public static ErrorCodes ExecuteFile(OsSession session, string fileName)
        {
            try
            {
                if (GetFileType(fileName) != FileType.Executable && GetFileType(fileName) != FileType.Other)
                {
                    LogMessage(session.UserName, "Attempt to execute a non-executable file.", fileName);
                    return ErrorCodes.AttemptToRunNonExecutable;
                }

                if (Files.ContainsKey(fileName)
                    && (CheckMandatoryAccessPermission(session, fileName) != ErrorCodes.NoError
                        || GetDiscretionalAccessPermission(session, fileName) != Rights.ReadWrite))
                {
                    LogMessage(session.UserName, "ACCESS VIOLATION: not enough rights to run executable.", fileName);
                    return ErrorCodes.NotEnoughPermissionsToExecuteFile;
                }

                Process.Start(fileName);
            }
            catch (Exception except)
            {
                LogMessage(session.UserName, "Error in executing file. Exception: " + except.Message, fileName);
                return ErrorCodes.NotEnoughPermissionsToExecuteFile;
            }

            LogMessage(session.UserName, "Successful execution of the file.", fileName);
            return ErrorCodes.NoError;
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- REGISTER FILE SYSTEM OBJECT 
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция регистрации уже существующего объекта файловой системы
             * Данная функция может регистрировать уже существующие файлы
            @return код ошибки, при её наличии
        */
        public static ErrorCodes RegisterFileSystemObject(OsSession session, string fileName)
        {
            try
            {
                if (Files.ContainsKey(fileName))
                {
                    LogMessage(session.UserName, "Attempt to register of already registered file.", fileName);
                    return ErrorCodes.NoError;
                }

                if (!File.Exists(fileName) && !Directory.Exists(fileName))
                {
                    LogMessage(session.UserName, "Attempt to register unexisted file.", fileName);
                    return ErrorCodes.AttemptToRegisterUnexistedFile;
                }

                var file = new FilePassport()
                {
                    FileName = fileName,
                    FileOpened = false,
                    FileTypeNum = GetFileType(fileName),
                    GroupName = Users[session.UserName].GroupName,
                    Key = GenerateKey(),
                    OwnerName = session.UserName,
                    SecrecyNum = Users[session.UserName].SecrecyNum,
                    ExclusiveUserAccessRights = new Dictionary<string, Rights>()
                };

                if (file.FileTypeNum == FileType.Directory)
                {
                    file.GroupRights = Users[session.UserName].GroupRightsDirectory;
                    file.OtherRights = Users[session.UserName].OthersRightsDirectory;
                }
                else if (file.FileTypeNum == FileType.Executable || file.FileTypeNum == FileType.TextFile)
                {
                    file.GroupRights = Users[session.UserName].GroupRightsFile;
                    file.OtherRights = Users[session.UserName].OthersRightsFile;
                }
                else
                {
                    LogMessage(session.UserName, "Attempt to register file that is not Directory, not Executable and not TextFile", fileName);
                    return ErrorCodes.AttemptToRegisterNonDirectoryAndNonExecutable;
                }

                Files.Add(fileName, file);
            }
            catch (Exception except)
            {
                LogMessage(session.UserName, "Error registration file. Exception: " + except.Message, fileName);
                return ErrorCodes.UnknownError;
            }

            LogMessage(session.UserName, "Successful registration of the file", fileName);
            return ErrorCodes.NoError;
        }
    }
}
